﻿//-----------------------------------------------------------------------
// <copyright file="GPXLib.cs" company="mkcoolsoft">
//     Copyright (c) mkcoolsoft. All rights reserved.
// </copyright>
// <author>Mario Kuhl</author>
// <revision>26.01.2014 Initial version</revision>
//-----------------------------------------------------------------------

namespace MKCoolsoft.GPXLib
{
    #region usings

    using System;
    using System.IO;
    using System.Collections.Generic;
    using System.Xml.Serialization;

    #endregion usings

    /// <summary>
    /// Types of GPS fix. none means GPS had no fix. To signify 
    /// "the fix info is unknown, leave out fixType entirely. <c>pps</c> = military signal used 
    /// </summary>
    public enum Fix
    {
        /// <summary>
        /// Represents none.
        /// </summary>
        none,

        /// <summary>
        /// Represents a 2d item.
        /// </summary>
        [XmlEnumAttribute("2d")]
        Item2d,

        /// <summary>
        /// Represents a 3d item.
        /// </summary>
        [XmlEnumAttribute("3d")]
        Item3d,

        /// <summary>
        /// Represents a <c>dgps</c> item.
        /// </summary>
        dgps,

        /// <summary>
        /// Represents a <c>pps</c> item.
        /// </summary>
        pps,
    }

    /// <summary>
    /// GPX is the root element in the XML file
    /// GPX documents contain a metadata header, followed by waypoints, routes, and tracks. 
    /// You can add your own elements to the extensions section of the GPX document. 
    /// </summary>
    [XmlRootAttribute("gpx", Namespace = "http://www.topografix.com/GPX/1/1", IsNullable = false)]
    public class GPXLib
    {
        #region private fields
        /// <summary>
        /// container instance for serialize / deserialize
        /// </summary>
        private GPXLib gpx;

        /// <summary>
        /// Metadata about the file.
        /// </summary>
        private Metadata metadataField;

        /// <summary>
        ///  A list of waypoints
        /// </summary>
        private List<Wpt> wptListField;

        /// <summary>
        /// A list of routes.
        /// </summary>
        private List<Rte> rteListField;

        /// <summary>
        /// A list of tracks.
        /// </summary>
        private List<Trk> trkListField;

        /// <summary>
        /// You can add extend GPX by adding your own elements from another schema here.
        /// </summary>
        private Extensions extensionsField;

        /// <summary>
        /// You must include the version number in your GPX document.
        /// </summary>
        private string versionField;

        /// <summary>
        /// You must include the name or URL of the software that created your GPX document. 
        /// This allows others to inform the creator of a GPX instance document that 
        /// fails to validate.
        /// </summary>
        private string creatorField;

        #endregion private fields

        #region constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="GPXLib"/> class.
        /// </summary>
        public GPXLib()
        {
            this.versionField = "1.1";
            this.metadataField = new Metadata();
            this.wptListField = new List<Wpt>();
            this.rteListField = new List<Rte>();
            this.trkListField = new List<Trk>();
            this.extensionsField = new Extensions();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GPXLib"/> class.
        /// This "private" constructor is for hiding the public constructor for 
        /// serialize / deserialize
        /// </summary>
        /// <param name="dummy">This is a dummy parameter.</param>
        private GPXLib(int dummy)
        {
        }

        #endregion constructors

        #region public properties

        /// <summary>
        /// Gets or sets the Metadata of the file.
        /// </summary>
        public Metadata Metadata
        {
            get
            {
                return this.metadataField;
            }

            set
            {
                this.metadataField = value;
            }
        }

        /// <summary>
        /// Gets or sets the waypoint list of the file.
        /// </summary>
        [XmlElementAttribute("wpt")]
        public List<Wpt> WptList
        {
            get
            {
                return this.wptListField;
            }

            set
            {
                this.wptListField = value;
            }
        }

        /// <summary>
        /// Gets or sets the route list of the file.
        /// </summary>
        [XmlElementAttribute("rte")]
        public List<Rte> RteList
        {
            get
            {
                return this.rteListField;
            }

            set
            {
                this.rteListField = value;
            }
        }

        /// <summary>
        /// Gets or sets the track list of the file.
        /// </summary>
        [XmlElementAttribute("trk")]
        public List<Trk> TrkList
        {
            get
            {
                return this.trkListField;
            }

            set
            {
                this.trkListField = value;
            }
        }

        /// <summary>
        /// Gets or sets the extensions of the file.
        /// </summary>
        public Extensions Extensions
        {
            get
            {
                return this.extensionsField;
            }

            set
            {
                this.extensionsField = value;
            }
        }

        /// <summary>
        /// Gets or sets the version of the file.
        /// </summary>
        [XmlAttributeAttribute("version")]
        public string Version
        {
            get
            {
                return this.versionField;
            }

            set
            {
                this.versionField = value;
            }
        }

        /// <summary>
        /// Gets or sets the creator of the file.
        /// </summary>
        [XmlAttributeAttribute("creator")]
        public string Creator
        {
            get
            {
                return this.creatorField;
            }

            set
            {
                this.creatorField = value;
            }
        }

        #endregion public properties

        #region public methods

        /// <summary>
        /// This procedure is for loading a GPX file.
        /// </summary>
        /// <param name="filename">Filename of the GPX file</param>
        public void LoadFromFile(string filename)
        {
            if (File.Exists(filename))
            {
                this.gpx = new GPXLib();

                try
                {
                    FileStream fs = new FileStream(filename, FileMode.Open);
                    XmlSerializer xmls = new XmlSerializer(typeof(GPXLib));
                    this.gpx = (GPXLib)xmls.Deserialize(fs);
                    fs.Close();

                    this.metadataField = this.gpx.Metadata;
                    this.wptListField = this.gpx.WptList;
                    this.rteListField = this.gpx.RteList;
                    this.trkListField = this.gpx.TrkList;
                    this.Extensions = this.gpx.Extensions;
                    this.versionField = this.gpx.Version;
                    this.creatorField = this.gpx.Creator;
                }
                catch
                {
                    throw new Exception("Can't deserialize");
                }
            }
            else
            {
                throw new Exception("File not found");
            }
        }

        /// <summary>
        /// This procedure is for saving a GPX file.
        /// </summary>
        /// <param name="filename">Filename of the GPX file</param>
        public void SaveToFile(string filename)
        {
            this.gpx = new GPXLib();

            this.gpx.Metadata = this.metadataField;
            this.gpx.WptList = this.wptListField;
            this.gpx.RteList = this.rteListField;
            this.gpx.TrkList = this.trkListField;
            this.gpx.Extensions = this.Extensions;
            this.gpx.Version = this.versionField;
            this.gpx.Creator = this.creatorField;

            try
            {
                FileStream fs = new FileStream(filename, FileMode.Create);
                XmlSerializer xmls = new XmlSerializer(typeof(GPXLib));
                xmls.Serialize(fs, this.gpx);
                fs.Close();
            }
            catch
            {
                throw new Exception("Can't serialize");
            }  
        }

        /// <summary>
        /// This procedure add a track point to the <see cref="TrkList"/> list.
        /// The <see cref="TrkList"/>, <see cref="Trkseg"/> and the 
        /// track point list will created if there are not exists.
        /// </summary>
        /// <param name="trackName">Name of the track</param>
        /// <param name="trackSegmentId">Index of the track segment list (start at 0)</param>
        /// <param name="trackPoint">The <see cref="Wpt"/> object </param>
        public void AddTrackPoint(string trackName, int trackSegmentId, Wpt trackPoint)
        {
            if (this.trkListField == null)
            {
                this.trkListField = new List<Trk>();

                Trk trk = new Trk(trackName);
                Trkseg trkseg = new Trkseg();
                trk.TrksegList = new List<Trkseg>();
                trkseg.TrkptList = new List<Wpt>();

                trk.TrksegList.Add(trkseg);
                trkseg.TrkptList.Add(trackPoint);

                this.trkListField.Add(trk);
            }
            else
            {
                Trk trk = null;
                Trkseg trkseg = null;

                for (int i = 0; i < this.trkListField.Count; i++)
                {
                    if (this.trkListField[i].Name == trackName)
                    {
                        trk = this.trkListField[i];
                        break;
                    }
                }

                if (trk != null)
                {
                    if (trk.TrksegList != null)
                    {
                        for (int i = 0; i < trk.TrksegList.Count; i++)
                        {
                            if (i == trackSegmentId)
                            {
                                trkseg = trk.TrksegList[i];
                                break;
                            }
                        }

                        if (trkseg != null)
                        {
                            if (trkseg.TrkptList == null)
                            {
                                trkseg.TrkptList = new List<Wpt>();
                            }

                            trkseg.TrkptList.Add(trackPoint);
                        }
                        else
                        {
                            trkseg = new Trkseg();
                            trkseg.TrkptList = new List<Wpt>();

                            trkseg.TrkptList.Add(trackPoint);
                            trk.TrksegList.Add(trkseg);
                        }
                    }
                }
                else
                {
                    trk = new Trk(trackName);
                    trkseg = new Trkseg();
                    trk.TrksegList = new List<Trkseg>();
                    trkseg.TrkptList = new List<Wpt>();

                    trk.TrksegList.Add(trkseg);
                    trkseg.TrkptList.Add(trackPoint);

                    this.trkListField.Add(trk);
                }
            }
        }

        /// <summary>
        /// This procedure add a route point to the <see cref="RteList"/> list.
        /// The <see cref="RteList"/> and the route point list will 
        /// created if there are not exists.
        /// </summary>
        /// <param name="routeName">Name of the route</param>
        /// <param name="routePoint">The <see cref="Wpt"/> object </param>
        public void AddRoutePoint(string routeName, Wpt routePoint)
        {
            if (this.rteListField == null)
            {
                this.rteListField = new List<Rte>();

                Rte rte = new Rte(routeName);
                rte.RteptList = new List<Wpt>();

                rte.RteptList.Add(routePoint);

                this.rteListField.Add(rte);
            }
            else
            {
                Rte rte = null;

                for (int i = 0; i < this.rteListField.Count; i++)
                {
                    if (this.rteListField[i].Name == routeName)
                    {
                        rte = this.rteListField[i];
                        break;
                    }
                }

                if (rte != null)
                {
                    rte.RteptList.Add(routePoint);
                }
                else
                {
                    rte = new Rte(routeName);
                    rte.RteptList = new List<Wpt>();

                    rte.RteptList.Add(routePoint);

                    this.rteListField.Add(rte);
                }
            }
        }

        #endregion public methods

    }
}
